package jmine.tec.script.persistent.driver;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jmine.tec.script.persistent.ScriptPersistenceMessages;
import jmine.tec.script.persistent.dao.ScriptLanguageIdentifier;
import jmine.tec.utils.io.IOUtils;
import bancosys.tec.datadigester.DataDigesterException;
import bancosys.tec.datadigester.DigesterDriver;
import bancosys.tec.datadigester.DigesterFile;

/**
 * Driver para leitura de scripts do hydra.
 * 
 * @author lundberg
 * @param <T> tipo de scriptBean criado
 */
public abstract class AbstractScriptDriver<T extends ScriptBean> implements DigesterDriver<T> {

    private static final String UTF_8 = "UTF-8";

    private final List<ScriptParameterParser<T>> headerParsers;

    private final HeaderExtractor headerExtractor;

    /**
     * Construtor
     * 
     * @param headerExtractor extrator das linhas de cabeçalho.
     */
    public AbstractScriptDriver(HeaderExtractor headerExtractor) {
        this.headerExtractor = headerExtractor;
        this.headerParsers = new ArrayList<ScriptParameterParser<T>>();
        this.addHeaderParser(new MnemonicoParser<T>());
        this.addHeaderParser(new DescricaoParser<T>());
        this.addHeaderParser(new FinalidadeParser<T>());
        this.addHeaderParser(new DatabaseIDParser<T>());
        this.addHeaderParser(new ImportParser<T>());
        this.addHeaderParser(new InjectBeanIntoParser<T>());
        this.addHeaderParser(new InjectIntoBeanParser<T>());
    }

    /**
     * Adiciona um parser de parâmetro de cabeçalho
     * 
     * @param headerParser headerParser
     */
    protected void addHeaderParser(ScriptParameterParser<T> headerParser) {
        this.headerParsers.add(headerParser);
    }

    /**
     * Efetua o parse do arquivo.
     * 
     * @param file file
     * @param driverProperties driverProperties
     * @return ScriptBean bean
     */
    public final T parse(DigesterFile file, Map<String, String> driverProperties) {
        try {
            return this.doParse(file, driverProperties);
        } catch (IOException e) {
            throw new DataDigesterException(ScriptPersistenceMessages.DRIVER_IOEXCEPTION.create(), e);
        }
    }

    /**
     * Efetuar o parse do script
     * 
     * @param file file
     * @param driverProperties driverProperties
     * @return bean
     * @throws IOException IOException
     */
    private T doParse(DigesterFile file, Map<String, String> driverProperties) throws IOException {
        T bean = this.createBean();
        byte[] bodyBytes = this.readBodyBytes(file);
        bean.setBody(new String(bodyBytes, UTF_8));
        this.parseHeader(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bodyBytes), UTF_8)), bean);
        bean.setLanguageIdentifier(this.getLanguageIdentifier());
        bean.setComplete(true);
        return bean;
    }

    /**
     * Extrai os bytes que compõe o corpo do script
     * 
     * @param file file
     * @return array de bytes
     * @throws IOException IOException
     */
    private byte[] readBodyBytes(DigesterFile file) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        IOUtils.copyFully(file.getInputStream(), output);
        return output.toByteArray();
    }

    /**
     * Cria o scriptBean apropriado ao driver
     * 
     * @return ScriptBean
     */
    protected abstract T createBean();

    /**
     * Obtém o identificador da linguagem lida por este driver.
     * 
     * @return identificador da linguagem
     */
    protected abstract ScriptLanguageIdentifier getLanguageIdentifier();

    /**
     * Lê o cabeçalho do arquivo.
     * 
     * @param reader reader
     * @param bean bean
     * @throws IOException IOException
     */
    private void parseHeader(BufferedReader reader, T bean) throws IOException {
        List<String> lines = this.headerExtractor.readLines(reader);
        for (String line : lines) {
            for (ScriptParameterParser<T> parser : this.headerParsers) {
                if (parser.parse(bean, line)) {
                    break;
                }
            }
        }
    }
}
